from flask import Flask, render_template, request, send_from_directory, redirect, url_for, abort, jsonify
import os
import socket
import datetime
import mimetypes
import re
import urllib.parse
import markdown
import bleach

app = Flask(__name__)

# 上传文件存储目录
UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'uploads')
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 500 * 1024 * 1024  # 限制上传大小为500MB

# 确保上传目录存在
os.makedirs(UPLOAD_FOLDER, exist_ok=True)

# 支持预览的文件类型
PREVIEWABLE_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml']
PREVIEWABLE_VIDEO_TYPES = ['video/mp4', 'video/webm', 'video/ogg']
PREVIEWABLE_AUDIO_TYPES = ['audio/mpeg', 'audio/ogg', 'audio/wav', 'audio/webm']
PREVIEWABLE_TEXT_TYPES = ['text/plain', 'text/markdown', 'text/csv', 'application/json', 'text/html', 'text/css', 'text/javascript']
PREVIEWABLE_PDF_TYPES = ['application/pdf']
PREVIEWABLE_MARKDOWN_TYPES = ['text/markdown', 'text/x-markdown']

# Office文档类型
OFFICE_DOCUMENT_TYPES = [
    # Excel
    'application/vnd.ms-excel', 
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    'application/vnd.oasis.opendocument.spreadsheet',
    # Word
    'application/msword',
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    'application/vnd.oasis.opendocument.text',
    # PowerPoint
    'application/vnd.ms-powerpoint',
    'application/vnd.openxmlformats-officedocument.presentationml.presentation',
    'application/vnd.oasis.opendocument.presentation'
]

# Office文档扩展名映射
OFFICE_EXTENSIONS = {
    '.xlsx': 'excel',
    '.xls': 'excel',
    '.csv': 'excel',
    '.docx': 'word',
    '.doc': 'word',
    '.pptx': 'powerpoint',
    '.ppt': 'powerpoint',
    '.odp': 'powerpoint',
    '.odt': 'word',
    '.ods': 'excel'
}

# Markdown文件扩展名
MARKDOWN_EXTENSIONS = ['.md', '.markdown', '.mdown', '.mkd']

# 文本文件的最大预览大小（字节）
MAX_TEXT_PREVIEW_SIZE = 100 * 1024 * 1024  # 100MB

def get_local_ip():
    """获取本机IP地址"""
    try:
        # 创建一个临时socket连接来获取本机IP
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(('8.8.8.8', 80))
        ip = s.getsockname()[0]
        s.close()
        return ip
    except:
        return '127.0.0.1'

def get_file_info(path):
    """获取文件信息"""
    stat = os.stat(path)
    file_size = stat.st_size
    # 格式化文件大小
    if file_size < 1024:
        size_str = f"{file_size} B"
    elif file_size < 1024 * 1024:
        size_str = f"{file_size/1024:.2f} KB"
    elif file_size < 1024 * 1024 * 1024:
        size_str = f"{file_size/(1024*1024):.2f} MB"
    else:
        size_str = f"{file_size/(1024*1024*1024):.2f} GB"
    
    # 获取文件上传时间
    modified_time = datetime.datetime.fromtimestamp(stat.st_mtime).strftime('%Y-%m-%d %H:%M:%S')
    
    # 获取MIME类型
    mime_type, _ = mimetypes.guess_type(path)
    if not mime_type:
        mime_type = "application/octet-stream"
    
    # 获取文件扩展名
    _, ext = os.path.splitext(path.lower())
    
    # 确定是否是Markdown文件
    is_markdown = ext in MARKDOWN_EXTENSIONS or mime_type in PREVIEWABLE_MARKDOWN_TYPES
    
    # 确定是否支持预览
    can_preview = mime_type in (PREVIEWABLE_IMAGE_TYPES + PREVIEWABLE_VIDEO_TYPES + 
                              PREVIEWABLE_AUDIO_TYPES + PREVIEWABLE_TEXT_TYPES + 
                              PREVIEWABLE_PDF_TYPES + OFFICE_DOCUMENT_TYPES) or is_markdown
    
    # 获取预览类型
    preview_type = ""
    if mime_type in PREVIEWABLE_IMAGE_TYPES:
        preview_type = "image"
    elif mime_type in PREVIEWABLE_VIDEO_TYPES:
        preview_type = "video"
    elif mime_type in PREVIEWABLE_AUDIO_TYPES:
        preview_type = "audio"
    elif mime_type in PREVIEWABLE_TEXT_TYPES and not is_markdown:
        preview_type = "text"
    elif is_markdown:
        preview_type = "markdown"
    elif mime_type in PREVIEWABLE_PDF_TYPES:
        preview_type = "pdf"
    elif mime_type in OFFICE_DOCUMENT_TYPES or ext in OFFICE_EXTENSIONS:
        preview_type = "office"
        # 如果通过扩展名可以更精确地确定文档类型，则使用扩展名判断
        if ext in OFFICE_EXTENSIONS:
            preview_type = f"office-{OFFICE_EXTENSIONS[ext]}"
    
    return {
        'size': size_str,
        'modified_time': modified_time,
        'mime_type': mime_type,
        'can_preview': can_preview,
        'preview_type': preview_type,
        'file_size': file_size,
        'extension': ext,
        'is_markdown': is_markdown
    }

def get_text_content(file_path, max_size=MAX_TEXT_PREVIEW_SIZE):
    """获取文本文件的内容，限制大小"""
    file_size = os.path.getsize(file_path)
    if file_size > max_size:
        with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
            content = f.read(max_size)
            content += "\n\n... 文件过大，仅显示前 100MB 内容 ..."
    else:
        with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
            content = f.read()
    return content

def render_markdown(content):
    """渲染Markdown内容为HTML"""
    # 使用Python-Markdown库渲染Markdown
    # 添加扩展以支持更多Markdown功能
    html = markdown.markdown(content, extensions=[
        'markdown.extensions.tables',
        'markdown.extensions.fenced_code',
        'markdown.extensions.codehilite',
        'markdown.extensions.toc'
    ])
    
    # 使用bleach清理HTML，防止XSS攻击
    allowed_tags = bleach.sanitizer.ALLOWED_TAGS + [
        'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'pre', 'code', 'blockquote',
        'hr', 'br', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'img'
    ]
    allowed_attrs = {**bleach.sanitizer.ALLOWED_ATTRIBUTES, **{
        'img': ['src', 'alt', 'title'],
        'a': ['href', 'alt', 'title'],
        'code': ['class'],
    }}
    
    clean_html = bleach.clean(
        html,
        tags=allowed_tags,
        attributes=allowed_attrs,
        strip=True
    )
    
    return clean_html

def get_office_preview_url(filename, file_path):
    """获取Office文档的在线预览URL"""
    # 获取文件扩展名
    _, ext = os.path.splitext(filename.lower())
    
    # 构建Office Online预览URL
    # 注意：这需要文件可以通过公网访问，本地局域网需要额外配置
    # 这里使用相对路径，实际部署时需要替换为完整URL
    file_url = f"/view/{urllib.parse.quote(filename)}"
    host = get_local_ip()
    port = 5000
    absolute_url = f"http://{host}:{port}{file_url}"
    
    # Microsoft Office Online Viewer
    ms_office_url = f"https://view.officeapps.live.com/op/view.aspx?src={urllib.parse.quote(absolute_url)}"
    
    # Google Docs Viewer (备选)
    google_docs_url = f"https://docs.google.com/viewer?url={urllib.parse.quote(absolute_url)}&embedded=true"
    
    return {
        'ms_office': ms_office_url,
        'google_docs': google_docs_url
    }

@app.route('/')
def index():
    """主页，显示上传表单和已上传文件列表"""
    files = []
    for filename in os.listdir(app.config['UPLOAD_FOLDER']):
        file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
        if os.path.isfile(file_path):
            file_info = get_file_info(file_path)
            files.append({
                'name': filename,
                'size': file_info['size'],
                'time': file_info['modified_time'],
                'type': file_info['mime_type'],
                'can_preview': file_info['can_preview'],
                'preview_type': file_info['preview_type']
            })
    
    # 按修改时间降序排序，最新的文件显示在前面
    files.sort(key=lambda x: x['time'], reverse=True)
    
    return render_template('index.html', files=files)

@app.route('/upload', methods=['POST'])
def upload_file():
    """处理文件上传"""
    if 'file' not in request.files:
        return redirect(request.url)
    
    file = request.files['file']
    
    if file.filename == '':
        return redirect(request.url)
    
    if file:
        filename = file.filename
        # 处理文件名重复情况
        if os.path.exists(os.path.join(app.config['UPLOAD_FOLDER'], filename)):
            base, extension = os.path.splitext(filename)
            filename = f"{base}_{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}{extension}"
        
        file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
        return redirect(url_for('index'))

@app.route('/download/<filename>')
def download_file(filename):
    """下载文件"""
    return send_from_directory(app.config['UPLOAD_FOLDER'], filename, as_attachment=True)

@app.route('/delete/<filename>')
def delete_file(filename):
    """删除文件"""
    file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    if os.path.exists(file_path):
        os.remove(file_path)
    return redirect(url_for('index'))

@app.route('/preview/<filename>')
def preview_file(filename):
    """文件预览"""
    file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    
    if not os.path.exists(file_path):
        abort(404)
    
    file_info = get_file_info(file_path)
    
    # 根据文件类型返回不同的预览内容
    if not file_info['can_preview']:
        return render_template('preview.html', 
                              filename=filename, 
                              preview_type="unsupported")
    
    preview_type = file_info['preview_type']
    
    if preview_type == "text":
        try:
            content = get_text_content(file_path)
            return render_template('preview.html', 
                                  filename=filename, 
                                  preview_type="text",
                                  content=content)
        except:
            return render_template('preview.html', 
                                  filename=filename, 
                                  preview_type="error")
    
    elif preview_type == "markdown":
        try:
            # 获取Markdown原始内容
            raw_content = get_text_content(file_path)
            # 渲染为HTML
            html_content = render_markdown(raw_content)
            return render_template('preview.html', 
                                 filename=filename, 
                                 preview_type="markdown",
                                 raw_content=raw_content,
                                 html_content=html_content)
        except Exception as e:
            print(f"Markdown渲染错误: {str(e)}")
            return render_template('preview.html', 
                                 filename=filename, 
                                 preview_type="error")
    
    # 对于Office文档，获取在线预览URL
    if preview_type.startswith("office"):
        preview_urls = get_office_preview_url(filename, file_path)
        office_type = preview_type.split('-')[1] if '-' in preview_type else 'generic'
        return render_template('preview.html', 
                             filename=filename, 
                             preview_type="office",
                             office_type=office_type,
                             preview_urls=preview_urls)
    
    # 对于图片、视频、音频和PDF，返回文件链接给前端
    return render_template('preview.html', 
                          filename=filename, 
                          preview_type=preview_type)

@app.route('/view/<filename>')
def view_file(filename):
    """直接查看文件内容（不作为附件下载）"""
    return send_from_directory(app.config['UPLOAD_FOLDER'], filename)

if __name__ == '__main__':
    host = get_local_ip()
    port = 5000
    print(f"服务器运行在: http://{host}:{port}")
    # 设置host为'0.0.0.0'使得服务器在所有网络接口上可访问
    app.run(host='0.0.0.0', port=port, debug=True) 